import java.io.*;
import java.net.*;

public class HubConnection {
	
	/* Helper class */
	private class SocketRead implements Runnable {
		private BufferedReader connRead;
		private HubConnection hub;
		
		SocketRead(HubConnection parent, Socket socket) {
			try {
				connRead = new BufferedReader(new InputStreamReader(conn
						.getInputStream()));
			} catch (IOException e) {
				System.out.println("Generic I/O error in nmdcLogin!");
				
				/* XXX Alert the GUI XXX */
				System.exit(1);
			}
			
			hub = parent;
		}
		
		public void run() {
			char[] read = new char[1024];
			int readBytes, idx;
			String cmd, temp;
			
			cmd = "";
			readBytes = 0;
			
			while (true) {
				try {
					readBytes = connRead.read(read);
				} catch (IOException e) {
					System.out.println("Error reading socket in SocketRead!");
					
					/* XXX Alert the main thread somehow XXX */
					System.exit(1);
				}
				
				if (readBytes > 0) {
					temp = new String(read, 0, readBytes);
					while ((idx = temp.indexOf('|')) != -1) {
						cmd = cmd + temp.substring(0, idx);
						hub.parseData(cmd);
						
						/* Infinite loops - DO NOT WANT! */
						temp = temp.substring(idx + 1);
						cmd = temp;
					}
					cmd = "";
				}
			}
		}
	}
	
	private static enum HighStatus { LOGIN, IDLE }
	private static enum LowStatus { GENKEY, GENINFO, CONNECTING, IDLE }

	/* Input output jazz */
	private Socket conn;
	private SocketRead connReader;
	private PrintWriter connWriter;
	
	/* Hub information */
	private String hubName;
	private String hubAddr;
	private int hubPort;
	
	/* Current status */
	private LowStatus lowStatus;
	private HighStatus highStatus;
	
	HubConnection(String host, int port) {
		hubPort = port;
		hubAddr = host;
		lowStatus = LowStatus.IDLE;
		highStatus = HighStatus.IDLE;
		conn = new Socket();
	}
	
	HubConnection() {
		conn = new Socket();
		lowStatus = LowStatus.IDLE;
		highStatus = HighStatus.IDLE;
	}
	
	private void parseCommand(String cmd, String body) {
		if (cmd.equals("$Lock")) {
			if (lowStatus == LowStatus.CONNECTING) {
				String sendKey;
				int i, llen;
				byte[] lock={0}, key;
				
				lowStatus = LowStatus.GENKEY;
				llen = body.indexOf(" Pk");
				try{
                    lock = body.substring(0, llen).getBytes("ISO-8859-1");
                } catch (UnsupportedEncodingException e) {
                    System.out.println("Your system sucks");
                }
				key = new byte[llen];
				
				key[0] = (byte) (lock[0] ^ lock[llen - 1] ^ lock[llen -2] ^ 5);
				
				for (i = 1; i < llen; i++) {
					key[i] = (byte) (lock[i] ^ lock[i - 1]);
					key[i] = (byte) ((((int)key[i] << 4) & 240) | ((key[i] >> 4) & 15));
				}
				
				key[0] = (byte) (((key[0] << 4) & 240) | ((key[0] >> 4) & 15));
				
				sendKey = new String(key);
				sendKey = sendKey.replace(
								Character.toString((char) 0), "/%DCN000%/");
				sendKey = sendKey.replace(
								Character.toString((char) 5), "/%DCN005%/");
				sendKey = sendKey.replace(
								Character.toString((char) 36), "/%DCN036%/");
				sendKey = sendKey.replace(
								Character.toString((char) 96), "/%DCN096%/");
				sendKey = sendKey.replace(
								Character.toString((char) 124), "/%DCN124%/");
				sendKey = sendKey.replace(
								Character.toString((char) 126), "/%DCN126%/");
				
				sendKey = new StringBuffer(sendKey).insert(
								0, "$Key ").toString();
				System.out.println(sendKey);
				connWriter.print(sendKey + "|$ValidateNick AeroDC|");
				connWriter.flush();
                System.out.println(sendKey);
                
			}
			else {
				/* Probably be alright to just ignore this */
				System.out.println("Wrong status for receiving this command!");
				System.exit(1);
			}
		}
		else if (cmd.equals("$HubName")) {
			System.out.println("In hubname: " + body);
		}
		else if (cmd.equals("$Hello")) {
			System.out.println("Hub has greeted us: " + body);
            connWriter.print("$Version 1,0091|$MyINFO $ALL AeroDC Now being written in Java <AeroDC is the shit>$ $1000$$70010135919133472$|");
            connWriter.flush();
            //connWriter.print("<AeroDC> Hellooooo, nurse!|");
            //connWriter.flush();
		}
		else if (cmd == "") {
			System.out.println("Someone spoke: " + body);
		}
		else {
			System.out.println("Unrecognized command: " + cmd);
		}
	}
	
	private void parseData(String msg) {
		int start_idx, end_idx;
		String cmd, body;
		
		start_idx = msg.indexOf("$");
		if (start_idx == -1) {
			parseCommand("", msg);
		}
		else {
			end_idx = msg.indexOf(" ", start_idx);
			cmd = msg.substring(start_idx, end_idx);
			body = msg.substring(end_idx + 1);
			parseCommand(cmd, body);
		}
	}
	
	private boolean connect() {
		try {
			conn.connect(new InetSocketAddress(hubAddr, hubPort), 5000);
		} catch (UnknownHostException e) {
			System.out.println("Unknown host!");
			
			/* XXX Alert the GUI XXX */
			System.exit(1);
		} catch (SocketTimeoutException e) {
			System.out.println("Timeout occurred!");
			
			/* XX Alert the GUI XXX */
			System.exit(1);
			
		} catch (IOException e) {
			System.out.println("Couldn't connect to host!");

			/* XXX Alert the GUI XXX */
			System.exit(1);
		}
		
		/* And create the writer */
		try {
			connWriter = new PrintWriter(conn.getOutputStream(), true);
		} catch (IOException e) {
			System.out.println("Couldn't create PrintWriter object!");
			
			/* XXX Alert the GUI XXX */
			System.exit(1);
		}
		
		/* Create the reader thread */
		connReader = new SocketRead(this, conn);
		if (connReader == null) {
			System.out.println("Couldn't create SocketRead object!");
			
			/* XXX Alert the GUI XXX */
			System.exit(1);
		}
		connReader.run();

		return true;
	}
	
	public boolean nmdcLogin() {
		if (conn.isConnected() == true) {
			System.out.println("Socket already connected!");
			return false;
		}
		
		highStatus = HighStatus.LOGIN;
		lowStatus = LowStatus.CONNECTING;
		if (connect() == false) {
			System.out.println("Connection failed!");
			return false;
		}
		
		return true;
	}
	
	/* Pls remove tis kthxbye */
	public static void main(String[] args) {
		HubConnection hubConn = new HubConnection(
					"uwdc.circleofcurrent.com", 411);
		//HubConnection hubConn = new HubConnection("129.97.247.245", 4111);
		hubConn.nmdcLogin();
		while (true) ;
	}
}